﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Reflection;
using CoolEngine.Gui;

namespace CoolEngine.Engine
{
    [AttributeUsage(AttributeTargets.Property)]
    public sealed class BoundVariableAttribute : Attribute
    {
        public string VariableName { get; set; }
        public string Module { get; set; }
        public string DefaultValue { get; set; }
        public BoundVariableAttribute():base()
        {

        }
        public BoundVariableAttribute(string variableName, string module,string defaultValue):base()
        {
            VariableName = variableName;
            Module = module;
            DefaultValue = defaultValue;
        }
    }
    public static class Variables
    {
        public static void ScanForBoundVariables()
        {
            ScanForBoundVariables(AppDomain.CurrentDomain.GetAssemblies());
        }
        public static void ScanForBoundVariables(Assembly[] assemblies)
        {
            foreach (Assembly assembly in assemblies)
            {
                foreach (Type type in assembly.GetTypes())
                {
                    foreach (PropertyInfo info in type.GetProperties(BindingFlags.Static))
                    {
                        object[] attributes = info.GetCustomAttributes(typeof(BoundVariableAttribute),false);
                        foreach (object boxed in attributes)
                        {
                            BoundVariableAttribute attribute = (BoundVariableAttribute)boxed;
                            Action<string> setDelegate = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), info.GetSetMethod(true), true);
                            Action<string, VariableDataContainer> setter = (s,vcd) => setDelegate(s);
                            Func<string> getDelegate = (Func<string>)Delegate.CreateDelegate(typeof(Func<string>), null, info.GetGetMethod(true), true);
                            Func<VariableDataContainer, string> getter = vdc=>getDelegate();
                            RegisterVariable(attribute.VariableName,attribute.Module, attribute.DefaultValue, null, setter, getter);
                        }
                    }
                }
            }
        }
        public class VariableDataContainer
        {
            public VariableDataContainer(string data)
            {
                Data = data;
            }
            public string Data { get; set; }
        }
        
        private static Dictionary<string, VariableDataContainer> variables = new Dictionary<string, VariableDataContainer>();
        private static Dictionary<string, GuiControl> controls = new Dictionary<string, GuiControl>();
        public static void RegisterControl(string name, GuiControl control)
        {
            controls.Add(name, control);
        }
        public static GuiControl GetControl(string name)
        {
            return controls[name];
        }
        /// <summary>
        /// Registers a variable to the system.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="defaultData">The default data.</param>
        public static void RegisterVariable(string name,string module,  string defaultData)
        {
            RegisterVariable(name,module, defaultData,null);
        }

        public static void RegisterVariable(string name, string module, string defaultData, Func<string, string> validityCheck, Action<string,VariableDataContainer> setter, Func<VariableDataContainer,string> getter)
        {
            VariableDataContainer container = new VariableDataContainer(defaultData);
            Func<string[], string> accessor;
            //if the validity check is null then no reason to test for it each time it's used
            if (validityCheck == null)
            {
                accessor = args =>
                {
                    if (args.Length == 1)
                    {
                        container.Data = args[0];
                        setter(args[0], container);                        
                        return null;
                    }
                    else if (args.Length == 0)
                    {
                        return getter(container);
                    }
                    else
                    {
                        throw new ArgumentOutOfRangeException("args", "Can't have more than one argument.");
                    }
                };
            }
            else
            {
                accessor = args =>
                {
                    if (args.Length == 1)
                    {
                        string arg = args[0];
                        string result = validityCheck(arg);
                        if (String.IsNullOrEmpty(result))
                        {
                            setter(args[0], container);
                            return null;
                        }
                        else
                        {
                            throw new ArgumentException(arg);
                        }
                    }
                    else if (args.Length == 0)
                    {
                        return getter(container);
                    }
                    else
                    {
                        throw new ArgumentOutOfRangeException("args", "Can't have more than one argument.");
                    }
                };
            }
            variables.Add(name, container);
            Command varCommand = new Command(name,module, 1,String.Format(CultureInfo.InstalledUICulture, "Gets or sets the {0} variable.", name), null, accessor);
            HardCodedCommandParser.RegisterCommand(name, varCommand);
        }
        public static void RegisterVariable(string name,string module, string defaultData, Func<string, string> validityCheck)
        {
            RegisterVariable(name,module, defaultData, validityCheck, ((s, vc) => vc.Data = s), vc => vc.Data); 
        }
        /// <summary>
        /// Gets the value of a variable. This way is cheaper than going through the command parser, which is mainly for scripting and the console.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        public static string GetVariable(string name)
        {
            return variables[name].Data;
        }
        /// <summary>
        /// Sets a variable.
        /// </summary>
        /// <param name="name">The name of the variable.</param>
        /// <param name="data">The data.</param>
        public static void SetVariable(string name, string data)
        {
            variables[name].Data = data;
        }
    }
}
